﻿Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Xml

Public Class ZuneProxy

    Public Port As Integer = 80
    Public Name As String = "ZuneProxy/1.0.*"
    Public dataProvider As String = "AMG"
    Public captureHosts As List(Of String)
    Public running As Boolean

    Private allSessions As List(Of Fiddler.Session)
    Private contentDB As ZuneDatabase

    Public Sub New()
        allSessions = New List(Of Fiddler.Session)
        captureHosts = New List(Of String)

        '//Add event handlers
        AddHandler (Fiddler.FiddlerApplication.BeforeRequest), AddressOf BeforeRequest
        AddHandler (Fiddler.FiddlerApplication.BeforeResponse), AddressOf BeforeResponse

        With captureHosts
            .Add("catalog.zune.net")
            .Add("image.catalog.zune.net")
            .Add("info.music.metaservices.microsoft.com")
            .Add("m.webtrends.com")
        End With
    End Sub

    Public Sub StartServer(ByRef _db As ZuneDatabase)
        Fiddler.FiddlerApplication.Startup(CInt(My.Settings.listenPort), True, False)
        frmMain.ToolStripSplitButton1.Text = "Proxy running..."
        frmMain.ToolStripSplitButton1.Image = My.Resources._112_Tick_Green_16x16_72
        frmMain.GlobalDisableToolStripMenuItem.Text = "Stop Proxy..."
        frmMain.GlobalDisableToolStripMenuItem.Image = My.Resources.delete_12x12

        contentDB = _db

        running = True
    End Sub

    Public Sub StopServer()
        Fiddler.FiddlerApplication.Shutdown()
        frmMain.ToolStripSplitButton1.Text = "Proxy stopped..."
        frmMain.ToolStripSplitButton1.Image = My.Resources._1385_Disable_16x16_72
        frmMain.GlobalDisableToolStripMenuItem.Text = "Start Proxy..."
        frmMain.GlobalDisableToolStripMenuItem.Image = My.Resources.command_link_16x16

        running = False
    End Sub

    Private Sub BeforeRequest(ByVal oS As Fiddler.Session)

        If captureHosts.Contains(oS.hostname) Then
            'Capture request and allow modifying
            oS.bBufferResponse = True
            'oS.utilDecodeRequest()
        End If

    End Sub

    Private Sub BeforeResponse(ByVal oS As Fiddler.Session)
        'oS.utilDecodeResponse()

        ProcessZuneRequest(oS)
    End Sub

    Public Sub ProcessZuneRequest(ByVal oS As Fiddler.Session)

        'Debug.Print(oS.fullUrl)

        Select Case oS.hostname
            Case "catalog.zune.net"
                '// Request artist or album information

                Dim UUID, req As String

                If oS.PathAndQuery.Contains("/music/artist/") Then
                    '// Request some artist information
                    UUID = ZuneHelpers.regex_UUID.Match(oS.PathAndQuery).Value
                    req = oS.PathAndQuery.Split("/")(5)

                    Select Case req
                        Case "biography"
                            '// Return artist's biography
                            Dim artist As ZuneArtist = contentDB.FindArtistByUUID(UUID)

                            If artist Is Nothing Then Exit Sub

                            oS.LoadResponseFromFile(DIR_RESPONDS + "artist_biography.xml")
                            oS.utilReplaceInResponse("#self#", oS.PathAndQuery)
                            oS.utilReplaceInResponse("#artistName#", artist.name)
                            oS.utilReplaceInResponse("#artistAuthor#", Environment.UserName)
                            oS.utilReplaceInResponse("#artistUUID#", UUID)
                            oS.utilReplaceInResponse("#artistBio#", ZuneHelpers.br2nl(Web.HttpUtility.HtmlEncode(artist.biography), True))
                            Debug.Print(oS.GetResponseBodyAsString)
                            Debug.Print("Artist: " + UUID + " | Biography served")

                        Case "images"
                            Dim artist As ZuneArtist = contentDB.FindArtistByUUID(UUID)

                            '// Return list of artist's images
                            Debug.Print("Artist: " + UUID + " | Image list served")

                            If Not artist Is Nothing Then
                                Dim fsEntry As New StreamReader(DIR_RESPONDS + "artist_images-entry.xml")
                                Dim fsInstance As New StreamReader(DIR_RESPONDS + "artist_images-instance.xml")
                                Dim entryTemplate As String = fsEntry.ReadToEnd
                                Dim instanceTemplate As String = fsInstance.ReadToEnd
                                Dim output As String = ""

                                oS.LoadResponseFromFile(DIR_RESPONDS + "artist_images.xml")
                                oS.utilReplaceInResponse("#self#", oS.PathAndQuery)
                                oS.utilReplaceInResponse("#selfNoVersion#", oS.PathAndQuery)

                                For Each img As ZuneImage In contentDB.Images.Values
                                    If img.artistUUID = UUID And img.type = ZuneImageType.Gallery Then
                                        Dim tmpEntry As String = entryTemplate

                                        tmpEntry = tmpEntry.Replace("#entryUUID#", img.UUID)
                                        tmpEntry = tmpEntry.Replace("#instances#", instanceTemplate)
                                        tmpEntry = tmpEntry.Replace("#instanceUUID#", img.UUID)
                                        tmpEntry = tmpEntry.Replace("#imageURI#", img.URI)
                                        tmpEntry = tmpEntry.Replace("#imageFormat#", "PNG")
                                        tmpEntry = tmpEntry.Replace("#imageWidth#", img.TempImage.Width)
                                        tmpEntry = tmpEntry.Replace("#imageHeight#", img.TempImage.Height)

                                        output += tmpEntry
                                    End If
                                Next

                                oS.utilReplaceInResponse("#entries#", output)

                            End If

                        Case ""
                            '// Return general artist information
                            Dim artist As ZuneArtist = contentDB.FindArtistByUUID(UUID)

                            If artist Is Nothing Then Exit Sub

                            oS.LoadResponseFromFile(DIR_RESPONDS + "artist.xml")
                            oS.utilReplaceInResponse("#self#", oS.PathAndQuery)
                            oS.utilReplaceInResponse("#artistUUID#", artist.uuid)
                            oS.utilReplaceInResponse("#artistName#", artist.name)

                            Dim bgImage As ZuneImage = Nothing
                            Dim deviceBgImage As ZuneImage = Nothing

                            '// Retrieve background images
                            For Each img In contentDB.Images.Values
                                If img.artistUUID = artist.uuid Then
                                    If img.type = ZuneImageType.Background Then
                                        bgImage = img
                                    ElseIf img.type = ZuneImageType.DeviceBackground Then
                                        deviceBgImage = img
                                    End If
                                End If
                            Next

                            If bgImage IsNot Nothing Then
                                '// Add background image
                                oS.utilReplaceInResponse("#imageBackgroundUUID#", bgImage.UUID)
                            End If

                            If deviceBgImage IsNot Nothing Then
                                '// Add device background image
                                oS.utilReplaceInResponse("#imageDeviceBackgroundUUID#", deviceBgImage.UUID)
                            End If

                            Debug.Print("Artist: " + UUID + " | General info served")
                    End Select
                End If

            Case "image.catalog.zune.net"
                '// Request a certain image from the collection

                Dim reqImage As String = ZuneHelpers.regex_UUID.Match(oS.PathAndQuery).Value

                If contentDB.Images.ContainsKey(reqImage) Then
                    Dim stream As New MemoryStream

                    If contentDB.Images(reqImage).isLocal Then
                        oS.LoadResponseFromFile(contentDB.Images(reqImage).URI)
                    Else
                        Dim tmpImg As Image = contentDB.Images(reqImage).TempImage
                        oS.LoadResponseFromFile(DIR_TEMP + contentDB.Images(reqImage).UUID + ".img")
                    End If
                End If

            Case "info.music.metaservices.microsoft.com"
                    '// Request metadata from WMP Metaservices

                    If oS.PathAndQuery.Contains("getmdrcd.aspx") Then
                        Dim doc As New XmlDocument

                        doc.LoadXml(oS.GetRequestBodyAsString)

                        With doc
                            '// First of all, get the request it to respond to
                            Dim requestID As String = doc.SelectSingleNode("//METADATA/MDQ-CD/mdqRequestID").InnerText
                            Dim artistName As String = doc.SelectSingleNode("//METADATA/MDQ-CD/album/artist/text").InnerText

                            If contentDB.Artists.ContainsKey(artistName) Then
                                Dim artist As ZuneArtist = contentDB.Artists(artistName)

                                oS.LoadResponseFromFile(DIR_RESPONDS + "artist_metadata.xml")
                                oS.utilReplaceInResponse("#requestID#", requestID)
                                oS.utilReplaceInResponse("#provider#", dataProvider)
                                oS.utilReplaceInResponse("#artistName#", artist.name)
                                oS.utilReplaceInResponse("#aID#", artist.fakeAlbumID)
                            End If
                        End With
                    End If

        End Select
    End Sub

#Region "Helper Functions"

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
  Public Structure INTERNET_CACHE_ENTRY_INFO
        Public dwStructSize As Integer

        Public lpszSourceUrlName As IntPtr
        Public lpszLocalFileName As IntPtr

        Public CacheEntryType As Integer
        Public dwUseCount As Integer
        Public dwHitRate As Integer
        Public dwSizeLow As Integer
        Public dwSizeHigh As Integer
        Public LastModifiedTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public ExpireTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public LastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public LastSyncTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public lpHeaderInfo As IntPtr
        Public dwHeaderInfoSize As Integer
        Public lpszFileExtension As IntPtr
        Public dwExemptDelta As Integer
    End Structure

    Const ERROR_NO_MORE_ITEMS As Integer = 259

    Public Sub ClearCache()
        ClearFolder(New DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.InternetCache)))
    End Sub

    Private Sub ClearFolder(ByVal diPath As DirectoryInfo)
        For Each fiCurrFile As FileInfo In diPath.GetFiles()
            Try
                If Not fiCurrFile.Name = "index.dat" Then fiCurrFile.Delete()
            Catch
            End Try
        Next

        For Each diSubFolder As DirectoryInfo In diPath.GetDirectories()
            ' Call recursively for all subfolders
            ClearFolder(diSubFolder)
        Next
    End Sub

#End Region

End Class

Public Module WinAPI

    <DllImport("wininet.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Public Function FindFirstUrlCacheEntry(<MarshalAs(UnmanagedType.LPTStr)> ByVal lpszUrlSearchPattern As String, ByVal lpFirstCacheEntryInfo As IntPtr, ByRef lpdwFirstCacheEntryInfoBufferSize As Integer) As IntPtr
    End Function

    <DllImport("wininet.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Public Function FindNextUrlCacheEntry(ByVal hEnumHandle As IntPtr, ByVal lpNextCacheEntryInfo As IntPtr, ByRef lpdwNextCacheEntryInfoBufferSize As Integer) As Boolean
    End Function

    <DllImport("wininet.dll")> _
    Public Function FindCloseUrlCache(ByVal hEnumHandle As IntPtr) As Boolean
    End Function

    <DllImport("wininet.dll", SetLastError:=True, CharSet:=CharSet.Auto, EntryPoint:="DeleteUrlCacheEntryA", CallingConvention:=CallingConvention.StdCall)> _
    Public Function DeleteUrlCacheEntry(<MarshalAs(UnmanagedType.LPWStr)> ByVal lpszUrlName As String) As Boolean
    End Function

End Module